package com.panopset.security;

import static com.panopset.Util.log;

import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.KeySpec;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.PBEParameterSpec;

import com.panopset.Alert;
import com.panopset.Base64;

/**
 *
 * Simple Java text encrypter. Uses PBEWithMD5AndDES algorithm from Sun.
 *
 * If you use the constructor that specifies number of columns, the encrypted
 * text will include line breads at that number of columns.  The default number
 * of columns is 80.
 *
 * <h5>References</h5>
 * <ul>
 * <li>
 * <a href="http://www.exampledepot.com/egs/javax.crypto/PassKey.html">
 * http://www.exampledepot.com/egs/javax.crypto/PassKey.html</a></li>
 * <li>
 * <a href=
 * "http://java.sun.com/developer/technicalArticles/Security/AES/AES_v1.html">
 * http://java.sun.com/developer/technicalArticles/Security/AES/AES_v1.html </a>
 * </li>
 * </ul>
 *
 */
public final class Encrypter {

    /**
     * Default algorithm is PBEWithMD5AndDES.
     */
    private static final String DEFAULT_ALGORITHM = "PBEWithMD5AndDES";

    /**
     * Default iteration count.
     */
    private static final int DEFAULT_ITER_COUNT = 19;

    /**
     * Decipher Cipher.
     */
    private final Cipher ecipher;

    /**
     * Encipher Cipher.
     */
    private final Cipher dcipher;

    /**
     * Encrypter constructor. Use this constructor for maximum control.
     *
     * @param passPhrase
     *            Pass phrase.
     * @param iterCount
     *            Iteration count.
     * @param salt
     *            Eight byte salt.
     * @param algorithm
     *            Default is <b>PBEWithMD5AndDES</b>.
     *
     */
    public Encrypter(final String passPhrase,
            final int iterCount, final Salt salt, final String algorithm) {
        try {
            KeySpec keySpec = new PBEKeySpec(passPhrase.toCharArray(), salt
                    .getValue(), iterCount);
            SecretKey key = SecretKeyFactory.getInstance(
                    algorithm).generateSecret(keySpec);
            ecipher = Cipher.getInstance(key.getAlgorithm());
            dcipher = Cipher.getInstance(key.getAlgorithm());

            AlgorithmParameterSpec paramSpec = new PBEParameterSpec(salt
                    .getValue(), iterCount);

            ecipher.init(Cipher.ENCRYPT_MODE, key, paramSpec);
            dcipher.init(Cipher.DECRYPT_MODE, key, paramSpec);
        } catch (Exception e) {
            log(e);
            throw new RuntimeException(e);
        }
    }

    /**
     * Encrypter constructor. For most typical applications. The default salt is
     * used. The default iteration count is used.
     *
     * @param passPhrase
     *            Pass phrase.
     */
    public Encrypter(final String passPhrase) {
        this(passPhrase, DEFAULT_ITER_COUNT, new Salt(),
                DEFAULT_ALGORITHM);
    }

    /**
     * Encrypt a String.
     *
     * @param str
     *            String to encrypt.
     * @return Encrypted result.
     */
    public String encrypt(final String str) {
        Alert.clrmsg();
        try {
            return Base64.encode(ecipher.doFinal(str.getBytes("UTF8")));
        } catch (Exception e) {
            log(e);
        }
        return null;
    }

    /**
     * Decrypt a String.
     *
     * @param str
     *            String to decrypt.
     * @return Decrypted str.
     */
    public String decrypt(final String str) {
        Alert.clrmsg();
        try {
            byte[] dec = Base64.decode(str);
            byte[] utf8 = dcipher.doFinal(dec);
            return new String(utf8, "UTF8");
        } catch (Exception e) {
            log(e);
            Alert.red("Check password, see log for details.");
        }
        return null;
    }
}
